/*
    ,--.                     ,--.     ,--.  ,--.
  ,-'  '-.,--.--.,--,--.,---.|  |,-.,-'  '-.`--' ,---. ,--,--,      Copyright 2024
  '-.  .-'|  .--' ,-.  | .--'|     /'-.  .-',--.| .-. ||      \   Tracktion Software
    |  |  |  |  \ '-'  \ `--.|  \  \  |  |  |  |' '-' '|  ||  |       Corporation
    `---' `--'   `--`--'`---'`--'`--' `---' `--' `---' `--''--'    www.tracktion.com

    Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
*/

namespace tracktion { inline namespace engine
{

/**
    Contains the limits of the various elements that can be added to an Edit.
    @see EngineBehaviour::getEditLimits
*/
struct EditLimits
{
    int maxNumTracks = 400;        /**< The maximum number of Track[s] an Edit can contain. */
    int maxClipsInTrack = 1500;    /**< The maximum number of Clip[s] a Track can contain. */
    int maxPluginsOnClip = 5;      /**< The maximum number of Plugin[s] a Clip can contain. */
    int maxPluginsOnTrack = 16;    /**< The maximum number of Plugin[s] a Track can contain. */
    int maxNumMasterPlugins = 4;   /**< The maximum number of master Plugin[s] and Edit can contain. */
};

/**
    Provides custom handlers to control various aspects of the engine's behaviour.
    Create a subclass of EngineBehaviour to customise how the engine operates
*/
class EngineBehaviour
{
public:
    EngineBehaviour() = default;
    virtual ~EngineBehaviour() = default;

    //==============================================================================
    // Plugin related settings:

    /// This will be called if the PluginManager doesn't know how to create a Plugin for the given info.
    virtual Plugin::Ptr createCustomPlugin (PluginCreationInfo)                     { return {}; }

    /// Gives the host a chance to do any extra configuration after a plugin is loaded
    virtual void doAdditionalInitialisation (ExternalPlugin&)                       {}

    /// Gives an opportunity to load custom plugins for those that have been registered as custom formats but not added to the list.
    virtual std::unique_ptr<juce::PluginDescription> findDescriptionForFileOrID (const juce::String&)   { return {}; }

    /// Should return if the given plugin is disabled or not.
    /// ExternalPlugins will use this to determine if they should load themselves or not.
    /// This can be called often so should be quick to execute.
    virtual bool isPluginDisabled (const juce::String& /*pluginID*/)                { return false; }

    /// Should implement a way of saving if plugin is disabled or not.
    /// N.B. only in use for ExternalPlugins at the moment.
    virtual void setPluginDisabled (const juce::String& /*pluginID*/, bool /*shouldBeDisabled*/)    {}

    /// Should the plugin be loaded. Normally plugins aren't loaded when Edit is for exporting
    /// or examining. Override this if you always need a plugin loaded
    virtual bool shouldLoadPlugin (ExternalPlugin&);

    /// Gives plugins an opportunity to save custom data when the plugin state gets flushed.
    virtual void saveCustomPluginProperties (juce::ValueTree&, juce::AudioPluginInstance&, juce::UndoManager*)  {}

    /** Return true if your application supports scanning plugins out of process.

        If you want to support scanning out of process, the allowing should be added
        to your JUCEApplication::initialise() function:

        void initialise (const juce::String& commandLine) override
        {
            if (PluginManager::startChildProcessPluginScan (commandLine))
                return;

             // ...continue as normal
    */
    virtual bool canScanPluginsOutOfProcess()                                       { return false; }

    //==============================================================================
    // Playback settings

    /// You may want to disable auto initialisation of the device manager if you
    /// are using the engine in a plugin.
    virtual bool autoInitialiseDeviceManager()                                      { return true; }

    /// In plugin builds, you might want to avoid adding the system audio devices
    /// and only use the host inputs.
    virtual bool addSystemAudioIODeviceTypes()                                      { return true; }

    /// If true, then the engine will attempt to open both an audio input and output.
    /// If false, it'll only open an output device. (This won't prevent your app
    /// opening an input device later if you allow the user to do that)
    virtual bool shouldOpenAudioInputByDefault()                                    { return true; }

    /// If this returns true, you must implement describeWaveDevices to determine the wave devices for a given device.
    /// If it's false, a standard, stereo pair layout will be automatically generated.
    virtual bool isDescriptionOfWaveDevicesSupported()                              { return false; }

    /// If isDescriptionOfWaveDevicesSupported returns true, this should be implemented to describe the wave devices
    /// for a given audio device.
    virtual void describeWaveDevices (std::vector<WaveDeviceDescription>&, juce::AudioIODevice&, bool /*isInput*/) {}

    /// Should return if plugins which have been bypassed should be included in the playback graph.
    /// By default this is false and bypassed plugins will still call processBypassed and introduce
    /// the same latency as if they weren't.
    /// But by returning false here, you can opt to remove them from the playback graph entirely
    /// which means they won't introduce latency which can be useful for tracking.
    virtual bool shouldBypassedPluginsBeRemovedFromPlaybackGraph()                  { return false; }

    /// Whether or not to include muted track contents in aux send plugins.
    /// Returning true here enables you to still listen to return busses when send tracks are
    /// muted or other tracks are soloed.
    virtual bool shouldProcessAuxSendWhenTrackIsMuted (AuxSendPlugin&)              { return true; }

    /// TEMPORARY: If enabled, real-time time-stretch Nodes will use a larger buffer and background
    /// thread to reduce audio CPU use.
    virtual bool enableReadAheadForTimeStretchNodes()                               { return false; }

    /// Should return true if the incoming timestamp for MIDI messages should be used.
    /// If this returns false, the current system time will be used (which could be less accurate).
    /// N.B. this is called from multiple threads, including the MIDI thread for every
    /// incoming message so should be thread safe and quick to return.
    virtual bool isMidiDriverUsedForIncommingMessageTiming()                        { return true; }
    virtual void setMidiDriverUsedForIncommingMessageTiming (bool)                  {}

    virtual bool shouldPlayMidiGuideNotes()                                         { return false; }

    virtual int getNumberOfCPUsToUseForAudio()                                      { return juce::jmax (1, juce::SystemStats::getNumCpus()); }

    /// Should muted tracks processing be disabled to save CPU
    virtual bool shouldProcessMutedTracks()                                         { return false; }

    /// Should track contents be audible whilst a recording is in progress
    virtual bool muteTrackContentsWhilstRecording()                                 { return false; }

    /// 0 = normal, 1 = high, 2 = realtime
    virtual void setProcessPriority (int /*level*/)                                 {}

    /// If implemented, this lets the behaviour determine exactly which file a new
    /// auto recording for the given track should be made to.
    virtual juce::File getFileForNewAudioRecording (Track&, const juce::String& fileExtension)  { (void) fileExtension; return {}; }

    /// If this is implemented, this folder will be used for the %project% pattern when
    /// parsing an audio input device's target filename.
    virtual juce::File getDefaultFolderForAudioRecordings (Edit&)                   { return {}; }

    /// The default filename that will be used for audio input devices if not overridden
    virtual juce::String getDefaultAudioRecordingFilePattern()                      { return "%projectdir%/%edit%_%track%_Take_%take%"; }

    //==============================================================================
    // Model-related options

    // some debate surrounds whether middle-C is C3, C4 or C5. In Tracktion we
    // default this value to 4
    virtual int getMiddleCOctave()                                                  { return 4; }

    virtual void setMiddleCOctave (int /*newOctave*/)                               {}

    // Notifies the host application that an edit has just been saved
    virtual void editHasBeenSaved (Edit&, juce::File)                               {}

    /// Should return the maximum number of elements that can be added to an Edit.
    virtual EditLimits getEditLimits()                                              { return {}; }

    virtual juce::ReferenceCountedObjectPtr<RackType> createPresetRackType (int /*index*/, Edit&)     { return {}; }

    /// If you have any special plugins that access items in the Edit, you need to return them
    virtual juce::Array<Exportable::ReferencedItem> getReferencedItems (ExternalPlugin&) { return {}; }

    /// If you have any special plugins that access items in the Edit, you need to reassign them
    virtual void reassignReferencedItem (Clip&, const Exportable::ReferencedItem&, ProjectItemID, double)  {}

    /// If you have any special clips that access items in the Edit, you need to return them
    virtual juce::Array<Exportable::ReferencedItem> getReferencedItems (Clip&)      { return {}; }

    /// If you have any special clips that access items in the Edit, you need to reassign them
    virtual void reassignReferencedItem (ExternalPlugin&, const Exportable::ReferencedItem&, ProjectItemID, double)  {}

    /// If this returns false, ClipSlot Clips won't be included in the playback graph
    /// and arranger track clips will always be audible.
    virtual bool areClipSlotsEnabled()                                              { return true; }

    // Default colour for notes. How this index maps to an actual colour is host dependant.
    // Waveform uses yellow, green, blue, purple, red, auto (based on key)
    virtual int getDefaultNoteColour()                                              { return 0; }

    virtual bool ignoreBWavTimestamps()                                             { return false; }

    virtual bool areAudioClipsRemappedWhenTempoChanges()                            { return true; }
    virtual void setAudioClipsRemappedWhenTempoChanges (bool)                       {}
    virtual bool areAutoTempoClipsRemappedWhenTempoChanges()                        { return true; }
    virtual void setAutoTempoClipsRemappedWhenTempoChanges (bool)                   {}
    virtual bool areMidiClipsRemappedWhenTempoChanges()                             { return true; }
    virtual void setMidiClipsRemappedWhenTempoChanges (bool)                        {}
    virtual bool arePluginsRemappedWhenTempoChanges()                               { return true; }
    virtual void setPluginsRemappedWhenTempoChanges (bool)                          {}

    /// If this returns true, it means that the length (in seconds) of one "beat" at
    /// any point in an edit is considered to be the length of one division in the current
    /// bar's time signature.
    /// So for example at 120BPM, in a bar of 4/4, one beat would be the length of a
    /// quarter-note (0.5s), but in a bar of 4/8, one beat would be the length of an
    /// eighth-note (0.25s)
    ///
    /// If false, then the length of one beat always depends only the current BPM at that
    /// point in the edit, so where the BPM = 120, one beat is always 0.5s, regardless of
    /// the time-sig.
    ///
    /// You shouldn't dynamically change this function's return value - just implement a
    /// function that always returns true or false.
    virtual bool lengthOfOneBeatDependsOnTimeSignature()                            { return true; }

    /// Called by the MidiList to create a MidiMessageSequence for playback.
    /// You can override this to add your own messages but should generally follow the
    /// procedure in MidiList::createDefaultPlaybackMidiSequence.
    virtual juce::MidiMessageSequence createPlaybackMidiSequence (const MidiList& list, MidiClip& clip, MidiList::TimeBase tb, bool generateMPE)
    {
        return MidiList::createDefaultPlaybackMidiSequence (list, clip, tb, generateMPE);
    }

    /// Must return the default looped sequence type to use.
    ///
    /// Current options are:
    /// 0: loopRangeDefinesAllRepetitions         - The looped sequence is the same for all repetitions including the first.
    /// 1: loopRangeDefinesSubsequentRepetitions  - The first section is the whole sequence, subsequent repitions are determined by the loop range.
    virtual int getDefaultLoopedSequenceType()                                      { return 0; }

    /// If this returns true, it means that newly inserted clips will automatically have a fade-in and fade-out of 3ms applied.
    virtual bool autoAddClipEdgeFades()                                             { return false; }

    struct LevelMeterSettings
    {
        int maxPeakAgeMs = 2000;
        float decaySpeed = 48.0f;
    };

    virtual LevelMeterSettings getLevelMeterSettings()                              { return {}; }
    virtual void setLevelMeterSettings (LevelMeterSettings)                         {}

    /// Determines the default properties of clips.
    struct ClipDefaults
    {
        bool useProxyFile = true;                                           ///< @see AudioClipBase::setUsesProxy
        ResamplingQuality resamplingQuality = ResamplingQuality::lagrange;  ///< @see setResamplingQuality::setResamplingQuality
    };

    /// Returns the defaults to be applied to new clips.
    virtual ClipDefaults getClipDefaults()                                          { return {}; }

    /// Allows a new clip to be customised.
    virtual void newClipAdded (Clip&, [[ maybe_unused ]] bool fromRecording)        {}

    struct ControlSurfaces
    {
        bool mackieMCU = true;
        bool mackieC4 = true;
        bool iconProG2 = true;
        bool tranzport = true;
        bool alphaTrack = true;
        bool remoteSL = true;
        bool remoteSLCompact = true;
        bool automap = true;
    };

    /// Return the control surfaces you want enabled in the engine
    virtual ControlSurfaces getDesiredControlSurfaces()                             { return {}; }

    /// Restore a custom control surface from custom XML
    virtual ControlSurface* getCustomControlSurfaceForXML (ExternalControllerManager&, const juce::XmlElement&)     { return {}; }
};

}} // namespace tracktion { inline namespace engine
